﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Syncfusion.Windows.Controls.Cells;
using Syncfusion.Windows.Shared;
using Syncfusion.Windows.Controls.Grid;
using System.Globalization;
using System.ComponentModel;
using SchoolDomain;
using Microsoft.Win32;
using System.IO;
using System.Windows.Controls.Primitives;

namespace SchoolClient
{
    /// <summary>
    /// Интерактивная логика для MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Поля
        /// <summary>
        /// Действие "создать журнал".
        /// </summary>
        public static RoutedCommand newJournalCommand = new RoutedCommand();
        /// <summary>
        /// Действие "открыть журнал".
        /// </summary>
        public static RoutedCommand openJournalCommand = new RoutedCommand();
        /// <summary>
        /// Действие "сохраниить журнал".
        /// </summary>
        public static RoutedCommand saveJournalCommand = new RoutedCommand();
        /// <summary>
        /// Действие "сохранить журнал как".
        /// </summary>
        public static RoutedCommand saveAsJournalCommand = new RoutedCommand();
        /// <summary>
        /// Действие "добавить урок".
        /// </summary>
        public static RoutedCommand addUnitCommand = new RoutedCommand();
        /// <summary>
        /// Действие "удалить урок".
        /// </summary>
        public static RoutedCommand deleteUnitCommand = new RoutedCommand();
        /// <summary>
        /// Действие "Выход".
        /// </summary>
        public static RoutedCommand applicationQuitCommand = new RoutedCommand();
        #endregion

        #region Константы
        /// <summary>
        /// Индекс первого столбца с уроком.
        /// </summary>
        private const int FIRST_UNIT_COLUMN_INDEX = 3;
        /// <summary>
        /// Индекс строки с именем месяца.
        /// </summary>
        private const int MONTH_NAME_ROW_INDEX = 0;
        /// <summary>
        /// Номер строки с датой урока.
        /// </summary>
        private const int UNIT_DATE_ROW_INDEX = 1;
        /// <summary>
        /// Индекс первой строки с фамилией ученика.
        /// </summary>
        private const int FIRST_STUDENT_ROW_INDEX = 2;
        /// <summary>
        /// Индекс столбца с фамилией ученика.
        /// </summary>
        private const int STUDENT_COLUMN_INDEX = 0;
        /// <summary>
        /// Индекс столбца со средним баллом.
        /// </summary>
        private const int AVERAGE_MARK_COLUMN_INDEX = 1;
        /// <summary>
        /// Индекс столбца с рейтингм учебной успешности.
        /// </summary>
        private const int RATING_COLUMN_INDEX = 2;
        /// <summary>
        /// Тип ячейки с переопределенным шаблоном.
        /// </summary>
        private const string DATA_TEMPLATE_CELL_TYPE = "DataTemplate";
        /// <summary>
        /// Шаблон ячейки-заголовка.
        /// </summary>
        private const string HEADER_TEMPLATE_KEY = "HeaderTemplate";
        /// <summary>
        /// Шаблон ячейки со средним баллом.
        /// </summary>
        private const string AVERAGE_MARK_TEMPLATE_KEY = "AverageMarkDataTemplate";
        /// <summary>
        /// Шаблон ячейки со значением рейтинга.
        /// </summary>
        private const string RATING_TEMPLATE_KEY = "RatingDataTemplate";
        /// <summary>
        /// Шаблон ячейки со значением оценки.
        /// </summary>
        private const string MARK_TEMPLATE_KEY = "MarkDataTemplate";
        /// <summary>
        /// Контекстное меню урока.
        /// </summary>
        private const string UNIT_CONTEXT_MENU_NAME = "unitContextMenu";
        /// <summary>
        /// Контекстное меню оценки.
        /// </summary>
        private const string PARAM_CONTEXT_MENU_NAME = "paramContextMenu";
        /// <summary>
        /// Контекстное окно с календарем.
        /// </summary>
        private const string CALENDAR_POPUP_RESOURCE_NAME = "calendarPopup";
        /// <summary>
        /// Расширение файла базы данных.
        /// </summary>
        private const string DEFAULT_SCHOOL_FILE_EXTENSION = ".sdf";
        /// <summary>
        /// Описание фильтра файла базы данных.
        /// </summary>
        private const string DEFAULT_SCHOOL_FILE_DESCRIPTION = "Файлы журналов (*.sdf)|*.sdf";
        /// <summary>
        /// Имя файла базы данных по умолчанию.
        /// </summary>
        private const string DEFAULT_SCHOOL_FILE_NAME = "School\\Безымянный.sdf";
        /// <summary>
        /// Заголовок столбца с фамилией ученика.
        /// </summary>
        private const string SURNAME_COLUMN_CAPTION = "Фамилия И.О.";
        /// <summary>
        /// Заголовок столбца со средним баллом.
        /// </summary>
        private const string AVERAGE_MARK_COLUMN_CAPTION = "Средний\nбалл";
        /// <summary>
        /// Заголовок столбца с рейтингом.
        /// </summary>
        private const string RATING_COLUMN_CAPTION = "Рейтинг";
        #endregion

        #region Методы
        /// <summary>
        /// Конструктор главного окна приложения.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            // Заполнить заголовки грида.
            ApplyGridTheme();
            grid.Model.ColumnCount = 3;
            grid.Model.RowCount = 2;
            grid.Model.HeaderColumns = 0;
            grid.Model.HeaderRows = 2;
            grid.Model.FrozenColumns = 3;
            grid.Model.FrozenRows = 2;
            grid.ColumnWidths[0] = 120;
            grid.ColumnWidths[1] = 60;
            grid.ColumnWidths[2] = 60;
            grid.Model.Options.AllowSelection = GridSelectionFlags.None;
            grid.Model.QueryCellText += new GridCellTextEventHandler(Model_QueryCellText);
            grid.Model.QueryCellFormattedText += new GridCellTextEventHandler(Model_QueryCellFormattedText);
            grid.Model.SaveCellFormattedText += new GridCellTextEventHandler(Model_SaveCellFormattedText);
            grid.Model.CoveredCells.Add(new CoveredCellInfo(0, 0, 1, 0));
            PrepareHeaderCell(0, 0, SURNAME_COLUMN_CAPTION, null);
            grid.Model.CoveredCells.Add(new CoveredCellInfo(0, 1, 1, 1));
            PrepareHeaderCell(0, 1, AVERAGE_MARK_COLUMN_CAPTION, null);
            grid.Model.CoveredCells.Add(new CoveredCellInfo(0, 2, 1, 2));
            PrepareHeaderCell(0, 2, RATING_COLUMN_CAPTION, null);

            // Подписаться на события коллекций предметной области. 
            StudentFactory.Instance.Students.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Students_CollectionChanged);
            UnitFactory.Instance.Units.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Units_CollectionChanged);
            MarkFactory.Instance.Marks.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Marks_CollectionChanged);
            newJournalCommand.Execute(null, this);
        }

        /// <summary>
        /// Найти строку с учеником.
        /// </summary>
        /// <param name="student">ученик</param>
        /// <returns>индекс строки с учеником</returns>
        private int FindStudentRow(Student student)
        {
            for (int rowIndex = FIRST_STUDENT_ROW_INDEX; rowIndex < grid.Model.RowCount; rowIndex++)
                if (grid.Model[rowIndex, STUDENT_COLUMN_INDEX].Tag == student)
                    return rowIndex;
            return -1;
        }
            
        /// <summary>
        /// Найти столбец с уроком.
        /// </summary>
        /// <param name="unit">урок</param>
        /// <returns>индекс столбца с уроком</returns>
        private int FindUnitColumn(Unit unit)
        {
            for (int columnIndex = FIRST_UNIT_COLUMN_INDEX; columnIndex < grid.Model.ColumnCount; columnIndex++)
                if (grid.Model[UNIT_DATE_ROW_INDEX, columnIndex].Tag == unit)
                    return columnIndex;
            return -1;
        }

        /// <summary>
        /// Найти объединенную ячейку месяца столбца.
        /// </summary>
        /// <param name="colIndex">индекс столбца</param>
        /// <returns>объединенная ячейка месяца столбца</returns>
        private CoveredCellInfo FindMonthCoveredCell(int colIndex)
        {
            DateTime unitDate = ((Unit)grid.Model[UNIT_DATE_ROW_INDEX, colIndex].Tag).UnitDate;
            CoveredCellInfo result = null;
            // Если дата слева принадлежит тому же месяцу, то расширить заголовок месяца слева.
            if ((colIndex > FIRST_UNIT_COLUMN_INDEX) && (grid.Model[UNIT_DATE_ROW_INDEX, colIndex - 1].Tag is Unit) &&
               ((Unit)grid.Model[UNIT_DATE_ROW_INDEX, colIndex - 1].Tag).UnitDate.Month == unitDate.Month)
                result = grid.Model.CoveredCells.GetCoveredCell(MONTH_NAME_ROW_INDEX, colIndex - 1);
            // Если дата справа принадлежит тому же месяцу, то расширить заголовок месяца справа.
            else if ((colIndex >= FIRST_UNIT_COLUMN_INDEX) && (colIndex < grid.Model.ColumnCount - 1) &&
                    (grid.Model[UNIT_DATE_ROW_INDEX, colIndex + 1].Tag is Unit) &&
                    ((Unit)grid.Model[UNIT_DATE_ROW_INDEX, colIndex + 1].Tag).UnitDate.Month == unitDate.Month)
                result = grid.Model.CoveredCells.GetCoveredCell(MONTH_NAME_ROW_INDEX, colIndex + 1);
            return result;
        }

        /// <summary>
        /// Обработать изменение коллекции оценок.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргменты события</param>
        void Marks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if ((e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) ||
                (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace))
                foreach (Mark mark in e.NewItems)
                {
                    int rowIndex = FindStudentRow(mark.Student);
                    int columnIndex = FindUnitColumn(mark.Unit);
                    if ((rowIndex > 0) && (columnIndex > 0))
                        SetMark(rowIndex, columnIndex, mark);
                }
        }

        /// <summary>
        /// Обработать изменение коллекции учеников.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        void Students_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                foreach (Student student in e.NewItems)
                    AddStudentRow(student);
            }
            if ((e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) &&
                (grid.Model.RowCount - FIRST_STUDENT_ROW_INDEX > 0))
            {
                grid.Model.RemoveRows(FIRST_STUDENT_ROW_INDEX, grid.Model.RowCount - FIRST_STUDENT_ROW_INDEX);
            }
        }

        /// <summary>
        /// Обработать изменение коллекции уроков.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        void Units_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                foreach (Unit unit in e.NewItems)
                {
                    AddUnitColumn(unit);
                    unit.PropertyChanged += new PropertyChangedEventHandler(unit_PropertyChanged);
                }
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
                foreach (Unit unit in e.OldItems)
                    RemoveUnitColumn(unit);
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
                grid.Model.RemoveColumns(FIRST_UNIT_COLUMN_INDEX, grid.Model.ColumnCount - FIRST_UNIT_COLUMN_INDEX);
        }

        /// <summary>
        /// Обработать изменение свойств урока.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        void unit_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "UnitDate")
            {
                RemoveUnitColumn((Unit)sender);
                AddUnitColumn((Unit)sender);
            }
        }

        /// <summary>
        /// Применить к гриду текущую тему.
        /// </summary>
        private void ApplyGridTheme()
        {
            string currentVisualStyle = SkinStorage.GetVisualStyle(this);
            IGridDataVisualStyle visualStyle;
            switch (currentVisualStyle)
            {
                case "Office2007Blue":
                    visualStyle = new GridDataBlueVisualStyle();
                    break;
                case "Office2007Black":
                    visualStyle = new GridDataBlackVisualStyle();
                    break;
                case "Office2007Silver":
                    visualStyle = new GridDataSilverVisualStyle();
                    break;
                case "Blend":
                    visualStyle = new GridDataBlendVisualStyle();
                    break;
                case "Office2003":
                    visualStyle = new GridDataSyncBlueVisualStyle();
                    break;
                default:
                    visualStyle = new GridDataDefaultGridVisualStyle();
                    break;
            }
            grid.Model.HeaderStyle.Background = visualStyle.HeaderBackgroundBrush;
            grid.Model.HeaderStyle.Foreground = visualStyle.HeaderForegroundBrush;
        }

        /// <summary>
        /// Добавить ячейку заголовка грида.
        /// </summary>
        /// <param name="left">левая граница</param>
        /// <param name="top">верхняя граница</param>
        /// <param name="right">правая граница</param>
        /// <param name="bottom">нижняя граница</param>
        /// <param name="header">заголовок</param>
        private void AddHeaderCell(int left, int top, int right, int bottom, string header)
        {
            grid.Model.CoveredCells.Add(new CoveredCellInfo(left, top, right, bottom));
            PrepareHeaderCell(left, top, header, null);
        }

        /// <summary>
        /// Инициализировать ячейку-заголовок.
        /// </summary>
        /// <param name="row">строка</param>
        /// <param name="column">столбец</param>
        /// <param name="value">отображаемое значение ячейки</param>
        /// <param name="tag">специальное значение ячейки</param>
        private void PrepareHeaderCell(int row, int column, string value, object tag)
        {
            grid.Model[row, column].CellValue = value;
            grid.Model[row, column].CellType = DATA_TEMPLATE_CELL_TYPE;
            grid.Model[row, column].CellTemplateKey = HEADER_TEMPLATE_KEY;
            grid.Model[row, column].Tag = tag;
        }

        /// <summary>
        /// Инициализировать ячейку с оценками.
        /// </summary>
        /// <param name="row">строка</param>
        /// <param name="column">столбец</param>
        /// <param name="mark">оценка</param>
        private void PrepareMarkCell(int row, int column, Mark mark)
        {
            grid.Model[row, column].CellType = DATA_TEMPLATE_CELL_TYPE;
            grid.Model[row, column].CellTemplateKey = MARK_TEMPLATE_KEY;
            grid.Model[row, column].CellValue = mark;
        }

        /// <summary>
        /// Инициализировать ячейку со средним баллом.
        /// </summary>
        /// <param name="row">строка</param>
        /// <param name="column">столбец</param>
        /// <param name="student">ученик</param>
        private void PrepareAverageMarkCell(int row, int column, Student student)
        {
            grid.Model[row, column].CellType = DATA_TEMPLATE_CELL_TYPE;
            grid.Model[row, column].CellTemplateKey = AVERAGE_MARK_TEMPLATE_KEY;
            grid.Model[row, column].CellValue = new AverageMarkCalculator(student);
        }

        /// <summary>
        /// Инициализировать ячейку с рейтингом учебной успешности.
        /// </summary>
        /// <param name="row">строка</param>
        /// <param name="column">столбец</param>
        /// <param name="student">ученик</param>
        private void PrepareRatingCell(int row, int column, Student student)
        {
            grid.Model[row, column].CellType = DATA_TEMPLATE_CELL_TYPE;
            grid.Model[row, column].CellTemplateKey = RATING_TEMPLATE_KEY;
            grid.Model[row, column].CellValue = new RatingCalculator(student);
        }

        /// <summary>
        /// Инициализировать ячейку для редактирования фамилии ученика.
        /// </summary>
        /// <param name="row">строка</param>
        /// <param name="column">столбец</param>
        /// <param name="student">значение ячейки</param>
        private void PrepareStudentCell(int row, int column, Student student)
        {
            grid.Model[row, column].CellType = "TextBox";
            grid.Model[row, column].CellValue = student.Name;
            grid.Model[row, column].Tag = student;
        }

        /// <summary>
        /// Добавить столбец для урока в указанную дату.
        /// </summary>
        /// <param name="unit">урок</param>
        private void AddUnitColumn(Unit unit)
        {
            DateTime unitDate = unit.UnitDate;

            // Найти столбец с ближайшей датой.
            int colIndex = FIRST_UNIT_COLUMN_INDEX;
            while (colIndex < grid.Model.ColumnCount)
            {
                if ((grid.Model[UNIT_DATE_ROW_INDEX, colIndex].Tag is Unit) && 
                    ((Unit)grid.Model[UNIT_DATE_ROW_INDEX, colIndex].Tag).UnitDate.CompareTo(unitDate) > 0)
                    break;
                colIndex++;
            }
            grid.Model.InsertColumns(colIndex, 1);

            // Заполнитиь заголовок с датой.
            PrepareHeaderCell(UNIT_DATE_ROW_INDEX, colIndex, unitDate.Day.ToString(), unit);

            // Заполнить заголовок с именем месяца.
            PrepareHeaderCell(MONTH_NAME_ROW_INDEX, colIndex, CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(unitDate.Month), unitDate);

            CoveredCellInfo ci = FindMonthCoveredCell(colIndex);
            // Если растянутая ячейка еще не добавлялась, то добавить.
            if (ci == null)
                grid.Model.CoveredCells.Add(new CoveredCellInfo(MONTH_NAME_ROW_INDEX, colIndex, MONTH_NAME_ROW_INDEX, colIndex));
            // Иначе изменить существующую...
            else
            {
                grid.Model.CoveredCells.Remove(ci);
                int left = Math.Min(ci.Left, colIndex);
                grid.Model.CoveredCells.Add(new CoveredCellInfo(MONTH_NAME_ROW_INDEX, left, MONTH_NAME_ROW_INDEX, left + ci.Width));
            }

            // Инициализировать ячейки оценок.
            int rowIndex = FIRST_STUDENT_ROW_INDEX;
            while (rowIndex < grid.Model.RowCount)
            {
                PrepareMarkCell(rowIndex, colIndex,
                    MarkFactory.Instance.GetMark((Student)grid.Model[rowIndex, STUDENT_COLUMN_INDEX].Tag, unit));
                rowIndex++;
            }
        }

        /// <summary>
        /// Удалить столбец для указанного урока.
        /// </summary>
        /// <param name="unit">урок</param>
        private void RemoveUnitColumn(Unit unit)
        {
            int colIndex = FindUnitColumn(unit);
            if (colIndex > 0)
            {
                CoveredCellInfo ci = grid.Model.CoveredCells.GetCoveredCell(MONTH_NAME_ROW_INDEX, colIndex);
                // Если растянутая ячейка еще ненайдена, то изменить ее.
                if (ci != null)
                {
                    grid.Model.CoveredCells.Remove(ci);
                    int left = ci.Left;
                    int right = ci.Right;
                    if (ci.Width > 1)
                    {
                        if (ci.Left == colIndex)
                            left = left + 1;
                        else
                            right = right - 1;
                        grid.Model.CoveredCells.Add(new CoveredCellInfo(MONTH_NAME_ROW_INDEX, left, MONTH_NAME_ROW_INDEX, right));
                    }
                }
                grid.Model.RemoveColumns(colIndex, 1);
            }
        }

        /// <summary>
        /// Добавить строку для ученика.
        /// </summary>
        /// <param name="student">ученик</param>
        private void AddStudentRow(Student student)
        {
            int studentRow = grid.Model.RowCount;
            grid.Model.InsertRows(studentRow, 1);

            // Подготовить стили отображения ячеек.
            PrepareStudentCell(studentRow, STUDENT_COLUMN_INDEX, student);
            PrepareAverageMarkCell(studentRow, AVERAGE_MARK_COLUMN_INDEX, student);
            PrepareRatingCell(studentRow, RATING_COLUMN_INDEX, student);
            int colIndex = FIRST_UNIT_COLUMN_INDEX;
            while (colIndex < grid.Model.ColumnCount)
            {
                PrepareMarkCell(studentRow, colIndex, MarkFactory.Instance.GetMark(student, 
                    (Unit)grid.Model[1, colIndex].Tag));
                colIndex++;
            }
            if (student.Name != "")
                PlaceStudentRow(studentRow, student.Name);
        }

        /// <summary>
        /// Разместить строку с новым учеником.
        /// </summary>
        /// <param name="rowIndex">индекс строки с учеником</param>
        /// <param name="studentName">имя ученика</param>
        private void PlaceStudentRow(int rowIndex, string studentName)
        {
            // Найти индекс строки со студентом, после которой нужно вставить нового студента.
            int currentRowIndex = FIRST_STUDENT_ROW_INDEX;
            while (currentRowIndex < grid.Model.RowCount - 1)
            {
                if ((currentRowIndex != rowIndex) &&
                    (grid.Model[currentRowIndex, STUDENT_COLUMN_INDEX].FormattedText.CompareTo(studentName) > 0))
                    break;
                currentRowIndex++;
            }

            // Если номер строки не совпадает, то перенести введенного студента на нужное место.
            if (currentRowIndex != rowIndex)
            {
                if (currentRowIndex > rowIndex)
                    currentRowIndex--;
                grid.Model.MoveRows(rowIndex, 1, currentRowIndex);
                grid.Model[currentRowIndex, STUDENT_COLUMN_INDEX].CellValue = studentName;
            }
        }

        /// <summary>
        /// Установить оценку.
        /// </summary>
        private void SetMark(int row, int column, Mark mark)
        {
            PrepareMarkCell(row, column, mark);
        }

        /// <summary>
        /// Обработать щелчок правой кнопкой мыши по ячейке с оценками.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void Cell_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            ContextMenu contextMenu = (ContextMenu)FindResource(PARAM_CONTEXT_MENU_NAME);
            contextMenu.PlacementTarget = this;
            contextMenu.IsOpen = true;
        }

        /// <summary>
        /// Выполнить действие "Создать журнал".
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void NewJournal_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            string databaseName = System.IO.Path.GetTempPath() + DEFAULT_SCHOOL_FILE_NAME;
            if (File.Exists(databaseName))
                File.Delete(databaseName);
            string directoryName = System.IO.Path.GetDirectoryName(databaseName);
            if (!Directory.Exists(directoryName))
                Directory.CreateDirectory(directoryName);
            Domain.Instance.Load(databaseName);
            StudentFactory.Instance.GetEmptyStudent();
            e.Handled = true;
        }

        /// <summary>
        /// Выполнить действие "Открыть журнал".
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void OpenJournal_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.DefaultExt = DEFAULT_SCHOOL_FILE_EXTENSION;
            openFileDialog.Filter = DEFAULT_SCHOOL_FILE_DESCRIPTION;
            if (openFileDialog.ShowDialog() == true)
            {
                Domain.Instance.Clear();
                Domain.Instance.Load(openFileDialog.FileName);
                StudentFactory.Instance.GetEmptyStudent();
            }
            e.Handled = true;
        }

        /// <summary>
        /// Выполнить действие "Сохранить журнал".
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void SaveJournal_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Domain.Instance.Save();
            e.Handled = true;
        }

        /// <summary>
        /// Обработать действие "Сохранить журнал как...".
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void SaveAsJournal_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.DefaultExt = DEFAULT_SCHOOL_FILE_EXTENSION;
            saveFileDialog.Filter = DEFAULT_SCHOOL_FILE_DESCRIPTION;
            if (saveFileDialog.ShowDialog() == true)
            {
                Domain.Instance.SaveAs(saveFileDialog.FileName);
                StudentFactory.Instance.GetEmptyStudent();
            }
            e.Handled = true;
        }

        /// <summary>
        /// Выполнить действие "Добавить урок".
        /// </summary>
        /// <param name="sender">источнис события</param>
        /// <param name="e">аргументы события</param>
        private void AddUnit_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            UnitFactory.Instance.GetNewUnit();
            e.Handled = true;
        }

        /// <summary>
        /// Выполнить действие "Удалить урок".
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void DeleteUnit_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if ((grid.CurrentCell.ColumnIndex >= FIRST_UNIT_COLUMN_INDEX) && 
                (grid.Model[grid.CurrentCell.RowIndex, grid.CurrentCell.ColumnIndex].Tag is Unit))
                UnitFactory.Instance.Delete((Unit)grid.Model[grid.CurrentCell.RowIndex, grid.CurrentCell.ColumnIndex].Tag);
            e.Handled = true;
        }

        /// <summary>
        /// Выполнить действие "Выход".
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void ApplicationQuit_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Application.Current.Shutdown();
            e.Handled = true;
        }

        /// <summary>
        /// Подготовить контекстное меню к отображению.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void ContextMenu_Opened(object sender, RoutedEventArgs e)
        {
            ContextMenu contextMenu = (ContextMenu)sender;
            if (!grid.Model.CurrentCellState.IsEmpty)
                contextMenu.Tag = grid.Model[grid.Model.CurrentCellState.RowIndex, 
                    grid.Model.CurrentCellState.ColumnIndex].CellValue;
        }

        /// <summary>
        /// Обработать результат работы контекстного меню.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void ContextMenu_Closed(object sender, RoutedEventArgs e)
        {
            ContextMenu contextMenu = (ContextMenu)sender;
            contextMenu.Tag = null;
        }

        /// <summary>
        /// Обработать изменение размера строки грида.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="args">аргументы события</param>
        void grid_ResizingRows(object sender, GridResizingRowsEventArgs args)
        {
            args.AllowResize = false;
            args.Handled = true;
        }

        /// <summary>
        /// Обработать завершение ввода в ячейке грида.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="args">аргументы события</param>
        private void grid_CurrentCellAcceptedChanges(object sender, Syncfusion.Windows.ComponentModel.SyncfusionRoutedEventArgs args)
        {
            if ((grid.CurrentCell.ColumnIndex == 0) && (grid.CurrentCell.Renderer.ControlText != ""))
            {
                int saveCurrentCellRowIndex = grid.CurrentCell.RowIndex;
                int saveCurrentCellColumnIndex = grid.CurrentCell.ColumnIndex;
                ((Student)grid.Model[saveCurrentCellRowIndex, saveCurrentCellColumnIndex].Tag).Name =
                    grid.CurrentCell.Renderer.ControlText;
                PlaceStudentRow(grid.CurrentCell.RowIndex, grid.CurrentCell.Renderer.ControlText);

                // Если заполнялась самая нижняя ячейка и она теперь не пустая, то нужно добавить новую пустую снизу.
                if ((saveCurrentCellRowIndex == grid.Model.RowCount - 1) && (grid.CurrentCell.Renderer.ControlText != ""))
                {
                    StudentFactory.Instance.GetEmptyStudent();
                    grid.CurrentCell.MoveTo(saveCurrentCellRowIndex, saveCurrentCellColumnIndex);
                    grid.CurrentCell.MoveDown();
                }
            }
        }

        /// <summary>
        /// Обработать событие на получение редактируемого текста ячейки грида.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        void Model_QueryCellText(object sender, GridCellTextEventArgs e)
        {
            if (e.Style.Tag is Student)
            {
                e.Text = ((Student)e.Style.Tag).Name;
                e.Handled = true;
            }
        }

        /// <summary>
        /// Обработать событие на сохранение данных из ячейки грида.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        void Model_SaveCellFormattedText(object sender, GridCellTextEventArgs e)
        {
            if (e.Style.Tag is Student)
            {
                ((Student)e.Style.Tag).Name = e.Style.FormattedText;
                e.Handled = true;
            }
        }

        /// <summary>
        /// Обработать событие на запрос отображаемой информации в ячейке грида.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        void Model_QueryCellFormattedText(object sender, GridCellTextEventArgs e)
        {
            // Если это колонка с фамилией студента, то показать фамилию.
            if (e.Style.Tag is Student)
            {
                e.Text = ((Student)e.Style.Tag).Name;
                e.Handled = true;
            }
            // Если это колонка со средним баллом, то вычислить и показать средний балл.
            if ((e.Style.ColumnIndex == AVERAGE_MARK_COLUMN_INDEX) && (e.Style.RowIndex >= FIRST_STUDENT_ROW_INDEX))
            {
                int sum = 0;
                int count = 0;
                for (int columnIndex = FIRST_UNIT_COLUMN_INDEX; columnIndex < grid.Model.ColumnCount; columnIndex++)
                {
                    if (((Mark)grid.Model[e.Style.RowIndex, columnIndex].CellValue).StandardMark != null)
                    {
                        sum += (int)((Mark)grid.Model[e.Style.RowIndex, columnIndex].CellValue).StandardMark;
                        count++;
                    }
                }
                if (count > 0)
                    e.Text = ((double)sum / (double)count).ToString();
                else
                    e.Text = "";
                e.Handled = true;
            }
        }

        /// <summary>
        /// Обработать щелчок правой кнопкой мыши по заголовку грида.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void DockPanel_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            if ((grid.CurrentCell.ColumnIndex >= FIRST_UNIT_COLUMN_INDEX) && (grid.CurrentCell.RowIndex == UNIT_DATE_ROW_INDEX))
            {
                ContextMenu contextMenu = (ContextMenu)FindResource(UNIT_CONTEXT_MENU_NAME);
                contextMenu.PlacementTarget = this;
                contextMenu.IsOpen = true;
            }
        }

        /// <summary>
        /// Обработать щелчок левой кнопкой мыши по заголовку грида.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void DockPanel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if ((grid.CurrentCell.ColumnIndex >= FIRST_UNIT_COLUMN_INDEX) && (grid.CurrentCell.RowIndex == UNIT_DATE_ROW_INDEX))
            {
                Popup popup = (Popup)FindResource(CALENDAR_POPUP_RESOURCE_NAME);
                if (sender is UIElement)
                {
                    popup.PlacementTarget = (UIElement)sender;
                    popup.StaysOpen = false;
                    popup.IsOpen = true;
                }
            }
        }

        /// <summary>
        /// Обработать событие на показ всплывающего окна календаря.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void Popup_Opened(object sender, EventArgs e)
        {
            Popup popup = (Popup)sender;
            if (!grid.Model.CurrentCellState.IsEmpty)
                popup.Tag = grid.Model[grid.Model.CurrentCellState.RowIndex,
                    grid.Model.CurrentCellState.ColumnIndex].Tag;
        }

        /// <summary>
        /// Обработать событие на закрытие всплывающего окна календаря.
        /// </summary>
        /// <param name="sender">источник события</param>
        /// <param name="e">аргументы события</param>
        private void Popup_Closed(object sender, EventArgs e)
        {
            Popup popup = (Popup)sender;
            popup.Tag = null;
            grid.Focus();
        }
        #endregion
    }

    /// <summary>
    /// Конвертер для заполнения ячейки грида из значения оценки.
    /// </summary>
    [ValueConversion(typeof(bool), typeof(Visibility))]
    public class BoolVisibilityConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if ((value != null) && (value is bool) && (bool)value)
                return Visibility.Visible;
            else
                return Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    /// <summary>
    /// Конвертер для выбора галочки нужной оценки в меню.
    /// </summary>
    [ValueConversion(typeof(int?), typeof(bool))]
    public class StandardMarkConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            int parameterValue;
            return (value is int) && int.TryParse(parameter.ToString(), out parameterValue) && ((int)value == parameterValue);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            int parameterValue;
            if ((value is bool) && (bool)value && int.TryParse(parameter.ToString(), out parameterValue))
                return parameterValue;
            else
                return null;
        }

        #endregion
    }

    /// <summary>
    /// Конвертер для отображения среднего балла.
    /// </summary>
    [ValueConversion(typeof(double), typeof(string))]
    public class AverageMarkConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if ((value is double) && ((double)value == 0.0))
                return "";
            else
                return string.Format("{0:0.00}", value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    /// <summary>
    /// Конвертер для отображения рейтинга учебной успешности.
    /// </summary>
    [ValueConversion(typeof(int?), typeof(string))]
    public class RatingConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is int)
                return string.Format("{0} ({1})", value,
                    Mark.GetRatingEffect((int)value).ToString("+0.0;-0.0;0.0"));
            else
                return "";
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    /// <summary>
    /// Конвертер для заголовка окна.
    /// </summary>
    [ValueConversion(typeof(string), typeof(string))]
    public class WindowTitleConverter : IValueConverter
    {
        /// <summary>
        /// Часть заголовка по умолчанию.
        /// </summary>
        private const string DEFAULT_HEADER_PART = "Система учета учебной успешности";

        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is string)
                return (string)value + " - " + DEFAULT_HEADER_PART;
            else
                return DEFAULT_HEADER_PART;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }

        #endregion
    }
}
